
namespace Patterns4Net.PatternEnforcer.Core
{
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;
    using System.Linq;
    using Patterns4Net.Attributes;
    using Patterns4Net.Attributes.DesignPatterns;

    /// <summary>
    /// Represents a result of checking process. 
    /// </summary>
    /// <remarks>
    /// <para>A result of checking process consists of number of 
    /// checked rules and eventual errors.</para>
    /// </remarks>
    [Immutable]
    [Composite]
    public sealed class CheckerResult
    {
        private static readonly CheckerResult validResult = new CheckerResult(1, CheckerError.EmptyArray);

        private IEnumerable<CheckerResult> children;

        private int checkedRulesCount;

        private IEnumerable<CheckerError> errors;

        public CheckerResult(int checkedRulesCount, IEnumerable<CheckerError> errors)
            : this(checkedRulesCount, errors, new CheckerResult[0])
        {
            Contract.Requires(errors != null);
            Contract.Requires(checkedRulesCount >= 0);
            Contract.Requires(checkedRulesCount >= errors.Count());
            Contract.Ensures(this.checkedRulesCount == checkedRulesCount);
            Contract.Ensures(Contract.ForAll(this.errors, e => errors.Contains(e)));
            Contract.Ensures(Contract.ForAll(errors, e => this.errors.Contains(e)));
        }

        public CheckerResult(IEnumerable<CheckerResult> children)
            : this(0, new CheckerError[0], children)
        {
            Contract.Requires(children != null);
        }

        public CheckerResult(int checkedRulesCount, IEnumerable<CheckerError> errors, IEnumerable<CheckerResult> children)
        {
            Contract.Requires(errors != null);
            Contract.Requires(checkedRulesCount >= 0);
            Contract.Requires(checkedRulesCount >= errors.Count());
            Contract.Ensures(this.checkedRulesCount == checkedRulesCount);
            Contract.Ensures(Contract.ForAll(this.errors, e => errors.Contains(e)));
            Contract.Ensures(Contract.ForAll(errors, e => this.errors.Contains(e)));

            this.checkedRulesCount = checkedRulesCount;
            this.errors = errors.ToArray();     // copy errors.
            this.children = children.ToArray();
        }

        public int CheckedRulesCount
        {
            get
            {
                return this.checkedRulesCount + this.children.Sum(x => x.CheckedRulesCount);
            }
        }


        public int ErrorsCount
        {
            get
            {
                return this.errors.Sum(e => e.Occurences) + this.children.Sum(x => x.ErrorsCount);
            }
        }

        public IEnumerable<CheckerError> Errors
        {
            get { return this.errors.Concat(this.children.SelectMany(c => c.Errors)); }
        }

        public bool IsValid
        {
            get { return this.errors.Count() == 0 && this.children.All(x => x.IsValid); }
        }

        /// <summary>
        /// Creates the result of checker which checks just one rule.
        /// In fact result of such checker is boolean and possibly error message
        /// rather than number of rules and broken rules and list of messages.
        /// </summary>
        /// <param name="valid">If set to <c>true</c> than result will contain zero errors, otherwise one error.</param>
        /// <param name="error">The possible error.</param>
        /// <returns>
        /// New <see cref="CheckerResult"/> instance.
        /// </returns>
        [SimpleFactoryMethod]
        public static CheckerResult CreateSimpleResult(bool valid, CheckerError error)
        {
            Contract.Ensures(Contract.Result<CheckerResult>() != null);
            Contract.Ensures(Contract.Result<CheckerResult>().checkedRulesCount == 1);

            // valid <=> no errors
            Contract.Ensures(valid || Contract.Result<CheckerResult>().ErrorsCount > 0);
            Contract.Ensures(valid == false || Contract.Result<CheckerResult>().ErrorsCount == 0);

            if (valid)
            {
                return new CheckerResult(1, CheckerError.EmptyArray);
            }
            else
            {
                return new CheckerResult(1, new[] { error });
            }
        }

        /// <summary>
        /// Returns just simple valid result with one checked rule and no errors.
        /// </summary>
        /// <returns>The valid result.</returns>
        [SimpleFactoryMethod]
        public static CheckerResult CreateValidResult()
        {
            return validResult;
        }

        [ContractInvariantMethod]
        private void Invariants()
        {
            // Children
            Contract.Invariant(this.children != null);
            Contract.Invariant(Contract.ForAll(this.children, x => x != null));

            // Counts
            Contract.Invariant(this.errors != null);
            Contract.Invariant(this.checkedRulesCount >= 0);
            Contract.Invariant(this.checkedRulesCount >= this.errors.Count());
        }
    }
}
